package org.maxgroundstation.simulation.service.gps;

import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.maxgroundstation.simulation.MaxRobot;
import org.maxgroundstation.simulation.service.Service;
import org.maxgroundstation.simulation.service.ServiceCommunication;
import org.maxgroundstation.simulation.service.ServiceDataFrame;
import org.maxgroundstation.simulation.service.ServiceSubscriber;
import org.maxgroundstation.simulation.service.ServiceUtil;

/**
 * The rover GPS service.
 */
public class GpsService extends Service {

    // Static members
    public static final String SERVICE_ID = ServiceUtil.getServiceID("gps");
    public static final long SLEEP_TIME = 1000L;
    
    // Data members
    private ScheduledThreadPoolExecutor executor;
    private DecimalFormat formatter;
    private DecimalFormat positionFormatter;
    private DateFormat dateFormatter;
    
    /**
     * Constructor
     * @param comm the service communication.
     */
    public GpsService(ServiceCommunication comm) {
        // Use Service constructor.
        super(comm);
        
        name = "GPS";
        description = "Displays the rover's GPS data";
        
        // Create text formatters.
        formatter = new DecimalFormat("0.00");
        positionFormatter = new DecimalFormat("0.0000000");
        dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssz");
        dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        
        // Start update thread execution.
        startExecution();
    }
    
    /**
     * Starts executing the GPS data sending.
     */
    private void startExecution() {
        if (executor != null) executor.shutdownNow();
        
        executor = new ScheduledThreadPoolExecutor(1);
        executor.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // Create formatted GPS data string.
                String gpsString = createGpsString();
                
                try {
                    sendGpsString(gpsString);
                }
                catch (Exception e) {
                    comm.log(e);
                }
            }       
        }, 0, SLEEP_TIME, TimeUnit.MILLISECONDS);
    }
    
    /**
     * Creates the formatted GPS data string.
     * @return GPS data string.
     */
    private String createGpsString() {
        MaxRobot robot = getServiceCommunication().getRobot();
        
        StringBuffer buff = new StringBuffer();
        
        // Add time stamp string.
        buff.append("time=");
        buff.append(dateFormatter.format(new Date()));
        buff.append(",");
        
        // Add longitude value.
        buff.append("lon=");
        buff.append(positionFormatter.format(robot.getLongitude()));
        buff.append(",");
        
        // Add latitude value.
        buff.append("lat=");
        buff.append(positionFormatter.format(robot.getLatitude()));
        buff.append(",");
        
        // Add heading value.
        buff.append("heading=");
        float headingDegrees = robot.getTravelHeading() * 180F / (float) Math.PI;
        buff.append(formatter.format(headingDegrees));
        buff.append(",");
        
        // Add speed value (note: GPS speed is always positive).
        buff.append("speed=");
        buff.append(formatter.format(Math.abs(robot.getSpeed())));
        buff.append(",");
        
        // Add elevation value.
        buff.append("elevation=");
        buff.append(formatter.format(Math.abs(robot.getElevation())));
        buff.append(",");
        
        // Add number of satellites.
        buff.append("satellites=");
        buff.append(robot.getSatellites());
        buff.append(",");
        
        // Add HDOP value.
        buff.append("hdop=");
        buff.append(formatter.format(robot.getHdop()));
        
        return buff.toString();
    }
    
    /**
     * Sends the GPS data string to service subscribers.
     * @param gpsString the formatted GPS data string.
     * @throws Exception if error sending GPS data string.
     */
    private void sendGpsString(String gpsString) throws Exception {
        Iterator<ServiceSubscriber> i = subscribers.iterator();
        while (i.hasNext()) {
            ServiceSubscriber subscriber = i.next();
            if (comm.getClientManager().checkClient(subscriber.getClientID())) {
                try {
                    //System.out.println("Sending GPS data string: '" + gpsString + "'");
                    comm.sendServicePost(subscriber.getCommType(), getID(), 
                            subscriber.getClientID(), gpsString.getBytes());
                }
                catch (Exception e) {
                    e.printStackTrace(System.err);
                    comm.log(e);
                }
            }
            else i.remove();
        }
    }
    
    @Override
    public String getID() {
        return SERVICE_ID;
    }

    @Override
    public void handleResponse(ServiceDataFrame dataFrame, int commType) throws Exception {
        // Do nothing.
    }

    @Override
    public boolean isDefaultService() {
        return false;
    }

    @Override
    public boolean isSubscribable() {
        return true;
    }

    @Override
    public void shutDown() {
        executor.shutdownNow();
    }
}